#include "MyTerrain.h"
#include "OgreMath.h"
#include <iostream>
#include "Page.h"
#include "Windows.h"
#include "OgreSceneManager.h"
#include "OgreManualObject.h"
#include "GdalLoader.h"
#include "gdal_priv.h"
#include "LCC.h"
#include "Triangle.h"

MyTerrain::MyTerrain(int edgeLength, const char* path, const char* lulcpath, LCC defaulttype, Ogre::SceneManager* manager, Ogre::ResourceGroupManager* rmanager, OgreInitializeSettings* init, const char* tername){
	this->edgeLength = edgeLength;
	this->path = path;
	this->lulcpath = lulcpath;
	this->defaulttype = defaulttype;
	this->manager = manager;
	this->rmanager = rmanager;
	this->init = init;
	this->name = tername;
	GdalLoader::registerGeotiffDriver();
}

MyTerrain::~MyTerrain(){
	delete[] lulcdata;
	delete terrain;
	delete lulc;
}

double MyTerrain::latToMeter(double latitude){
	//return latitude * EARTHRADIUSMETER * Ogre::Math::Cos(Ogre::Degree(latitude), false);
	return latitude*60*1852;
}

double MyTerrain::longToMeter(double longitude, double latitude){
	//return longitude * 2 * Ogre::Math::PI * EARTHRADIUSMETER * Ogre::Math::Cos(Ogre::Degree(latitude), false)/360;
	return longitude*60*1852;
}

double MyTerrain::meterToLat(double meter){
	return (meter/60)/1852;
}

// x = W/E = long
// y = elevation
// z = N/E = lat

void MyTerrain::createTerrain(){
	terrain = new GdalLoader();
	GDALRasterBand* band = terrain->openFile(path.c_str())->GetRasterBand(1);
	int xmax = band->GetXSize();
	int ymax = band->GetYSize();
	int i = 0;
	for (int y = 0; y < ymax; y += edgeLength){
		int yfar = edgeLength;
		if (y + edgeLength > ymax) yfar = ymax - y;
		for (int x = 0; x <= xmax; x += edgeLength){
			int xfar = edgeLength;
			if (x + edgeLength > xmax) xfar = xmax - x;
			band->AdviseRead(x, y, xfar, yfar, xfar, yfar, GDT_Float32, NULL);
			float* buffer = new float[xfar*yfar];
			band->RasterIO(GF_Read, x, y, xfar, yfar, buffer, xfar, yfar, GDT_Float32, 0, 0);
			Page* page = createPage(buffer, x, y, xfar, yfar, 3, i);
			pageVector.push_back(page);
			delete page;
			delete[] buffer;
			i++;
			return;
		}
	}
}

Page* MyTerrain::createPage(float* data, double xstart, double ystart, double xmax, double ymax, int step, int id){	
	double xstartm, ystartm, xmaxm, ymaxm;
	terrain->pixelToGeo(xstart, ystart, &xstartm, &ystartm);
	terrain->pixelToGeo(xmax, ymax, &xmaxm, &ymaxm);
	xstartm = latToMeter(xstartm);
	xmaxm = latToMeter(xmaxm);
	ystartm = latToMeter(ystartm);
	ymaxm = latToMeter(ymaxm);
	double minY = DBL_MAX;
	double maxY = -DBL_MAX;
	Page* rv = new Page(xstartm, ymaxm, xmaxm, ystartm, manager, step, init, id, rmanager, name.c_str());
	for (int curlod = 1; curlod <= step; curlod++){
		std::vector<double> npoints;
		bool breaky = false;
		int oldy = 0;
		for (int y = 0; true; y += curlod){
			int dy = y - oldy;
			oldy = y;
			std::vector<double> curpoints;
			bool breakx = false;
			int j = 0;
			int oldx = 0;
			for (int x = 0; true; x += curlod){
				int dx = x - oldx;
				oldx = x;
				int i = y * xmax + x;
				int pixelx = xstart + x;
				int pixelz = ystart + y;
				double xdeg, zdeg;
				terrain->pixelToGeo(pixelx, pixelz, &xdeg, &zdeg);
				double SEz = latToMeter(zdeg);
				double SEx = longToMeter(xdeg, zdeg);
				double SEy = (double) data[i];
				if (SEy > maxY) maxY = SEy;
				if (SEy < minY) minY = SEy;
										//std::cout << x << " here\n";
				//std::cout << "Adding point: " << pixelx << " pixel x " << pixelz << " pixel Z " << SEy << " elevation\n";
				
				if (y > 0){
					if (x > 0){
						Ogre::Vector3 vecSE(SEx, SEy, SEz);
						//std::cout << x << " here\n";
						double SWx, SWz;
						terrain->pixelToGeo(pixelx - dx, pixelz, &SWx, &SWz);
						double SWy = curpoints.at(j-1);
						SWx = longToMeter(SWx, SWz);
						SWz = latToMeter(SWz);
						Ogre::Vector3 vecSW(SWx, SWy, SWz);

						double NWx, NWz;
						terrain->pixelToGeo(pixelx - dx, pixelz - dy, &NWx, &NWz);
						double NWy = npoints.at(j-1);
						NWx = longToMeter(NWx, NWz);
						NWz = latToMeter(NWz);
						Ogre::Vector3 vecNW(NWx, NWy, NWz);

						double NEx, NEz;
						terrain->pixelToGeo(pixelx, pixelz - dy, &NEx, &NEz);
						double NEy = npoints.at(j);
						NEx = longToMeter(NEx, NEz);
						NEz = latToMeter(NEz);
						Ogre::Vector3 vecNE(NEx, NEy, NEz);
						//LCC NW = findLCC(reduceVector(vecNE), reduceVector(vecNW), reduceVector(vecSW));
						//LCC SE = findLCC(reduceVector(vecNE), reduceVector(vecSW), reduceVector(vecSE));
						LCC NW = pasture;
						LCC SE = pasture;

						Triangle* triNW = new Triangle();
						triNW->a = vecSW;
						triNW->b = vecNW;
						triNW->c = vecNE;

						rv->fillMap(NW, triNW, curlod);

						Triangle* triSE = new Triangle();
						triSE->a = vecSE;
						triSE->b = vecSW;
						triSE->c = vecNE;
						rv->fillMap(SE, triSE, curlod);
	
					}
				}
				j++;
				curpoints.push_back(SEy);

				if (breakx) break;
				if (x == xmax-1) break;
				if (x < xmax){
					if (x + curlod > xmax-1) {
						x = xmax-1-curlod;
						breakx = true;
					}
				}
			}
			npoints = curpoints;

			if (breaky) break;
			if (y == ymax-1) break;
			if (y < ymax) {
				if (y + curlod > ymax-1) {
					y = ymax-1-curlod;
					breaky = true;
				}
			}
		}
	}
	rv->setElevationExtrema(minY, maxY);
	rv->compile();
	return rv;
}

void MyTerrain::createLulcPoints(){
	lulc = new GdalLoader();
	GDALDataset* lulcset = lulc->openFile(lulcpath.c_str());
	GDALRasterBand* band = lulcset->GetRasterBand(1);
	int xmax = band->GetXSize();
	int ymax = band->GetYSize();
	band->AdviseRead(0, 0,xmax, ymax, xmax, ymax, GDT_Byte, NULL);
	lulcdata = new unsigned char[xmax*ymax];	
	band->RasterIO(GF_Read, 0, 0, xmax, ymax, lulcdata, xmax, ymax, GDT_Byte, 0, 0);
}

LCC MyTerrain::findLCC(Ogre::Vector3 a, Ogre::Vector3 b, Ogre::Vector3 c){
	Ogre::Vector3 mid1 = a.midPoint(b);
	Ogre::Vector3 mid2 = b.midPoint(c);

	Ogre::Vector3 median1dir = c - mid1;
	Ogre::Vector3 median2dir = a - mid2;
	
	Ogre::Ray median1 (mid1, median1dir);
	
	Ogre::Vector3 perp = median1dir;
	perp.z = 1000.0;
	Ogre::Plane plane(mid2, median2dir, perp);
	Ogre::Vector3 inter = median1.getPoint(median1.intersects(plane).second);
	//Ogre::Vector2 inter2 = reduceVector(inter);
	double xgeo = meterToLat(inter.x);
	double ygeo = meterToLat(inter.z);
	double pixelx, pixely;
	terrain->geoToPixel(xgeo, ygeo, &pixelx, &pixely);
	return accessLCC((int)pixelx, (int)pixely);
}

LCC MyTerrain::accessLCC(int x, int y){
	int width = lulc->getWidth();
	int height = lulc->getHeight();
	if (x > width) x = width;
	if (x < 0) x = 0;
	if (y > height) y = height;
	if (y < 0) y = 0;
	int i = (width-1) * y;
	i += x;
	return (LCC) lulcdata[i];
}

Ogre::Vector3 MyTerrain::reduceVector(Ogre::Vector3 in){
	return Ogre::Vector3(in.x, 0, in.z);
}